import time
from pymongo import MongoClient


# from XSubmit.base_logger import logger


class XSubmitDB:
    class XSubmitDBExceptions:
        class ObjectNotFound(Exception):
            pass

        class ObjectExist(Exception):
            pass

        class ArgsError(Exception):
            pass

    client_link = None
    collections = ['school', 'user', 'class_', 'post', 'box']
    collection_id = {
        'school': 'sid', 'user': 'uid', 'class_': 'cid', 'post': 'pid', 'box': 'bid'
    }

    # 递归删除None节点
    def delete_none_node(self, a: dict) -> dict:
        result = {}
        for d in a:
            if type(a[d]) is dict:
                result[d] = self.delete_none_node(a[d])
            elif a[d] is not None:
                result[d] = a[d]
        return result

    @staticmethod
    def delete_id_node(a: dict) -> dict:
        if '_id' in a:
            del a['_id']
        return a

    @staticmethod
    def delete_user_password(user: dict) -> dict:
        if 'password' in user:
            del user['password']
        return user

    def __init__(self):
        if not self.client_link:
            self.client = MongoClient()
        else:
            self.client = MongoClient(self.client_link)
        self.db = self.client.xsubmit

    def db_get_one(self, col_name: str, args: dict):
        result = list(self.db[col_name].find(args))
        if len(result) == 0:
            raise XSubmitDB.XSubmitDBExceptions.ObjectNotFound
        return self.delete_id_node(result[0])

    def db_get(self, col_name: str, args: dict):
        data = list(self.db[col_name].find(args))
        result = []
        for d in data:
            result.append(self.delete_id_node(d))
        return result

    def database_clear_all(self):
        for col in self.collections:
            self.db[col].drop()

    def database_init(self):
        for col in self.collections:
            if col in self.collection_id and \
                    len(list(self.db[col].find({'_id': self.collection_id[col]}, {'_id': 0}))) == 0:
                self.db[col].insert_one({
                    '_id': self.collection_id[col],
                    'sequence_value': 1,
                })

    def get_next_id(self, col_name: str) -> int:
        col = self.db[col_name]
        if col_name not in self.collection_id:
            return 0
        result = col.find_one_and_update({'_id': self.collection_id[col_name]},
                                         {'$inc': {'sequence_value': 1}})
        return result['sequence_value']

    def school_get(self, sid: int) -> dict:
        return self.db_get_one('school', {'sid': sid})

    def school_get_by_name(self, name: str):
        return self.db_get_one('school', {'name': name})

    def school_update_info(self, sid: int, name: str = None, head: str = None) -> dict:
        school = self.school_get(sid)
        school['info']['name'] = name
        school['info']['head'] = head
        self.db.school.update_one({'sid': sid}, {'$set': {'info': school['info']}})
        return school

    def school_new(self, name: str, head: str = None) -> dict:
        try:
            self.school_get_by_name(name)
            raise XSubmitDB.XSubmitDBExceptions.ObjectExist
        except XSubmitDB.XSubmitDBExceptions.ObjectNotFound:
            pass
        school = {
            'sid': self.get_next_id('school'),
            'info': {
                'name': name,
                'head': head
            }
        }
        school = self.delete_none_node(school)
        self.db.school.insert_one(school)
        school = self.delete_id_node(school)
        return school

    def school_remove(self, sid: int) -> dict:
        school = self.school_get(sid)
        self.db.school.delete_one({'sid': sid})
        return school

    def class_get(self, cid: int) -> dict:
        return self.db_get_one('class_', {'cid': cid})

    def class_get_by_name(self, name: str):
        return self.db_get_one('class_', {'name': name})

    def class_update_info(self, cid: int, name: str = None, head: str = None) -> dict:
        class_ = self.class_get(cid)
        class_['info']['name'] = name
        class_['info']['head'] = head
        self.db.class_.update_one({'cid': cid}, {'$set': {'info': class_['info']}})
        return class_

    def class_new(self, sid: int, name: str, head: str = None) -> dict:
        class_ = {
            'cid': self.get_next_id('class_'),
            'sid': sid,
            'info': {
                'name': name,
                'head': head
            }
        }
        class_ = self.delete_none_node(class_)
        self.db.class_.insert_one(class_)
        class_ = self.delete_id_node(class_)
        return class_

    def class_remove(self, cid: int) -> dict:
        class_ = self.class_get(cid)
        self.db.class_.delete_one({'cid': cid})
        return class_

    def user_get_with_password(self, uid: int) -> dict:
        return self.db_get_one('user', {'uid': uid})

    def user_get(self, uid: int) -> dict:
        return self.delete_user_password(self.db_get_one('user', {'uid': uid}))

    def user_update_info(self, uid: int,
                         name: str = None, email: str = None, head: str = None) -> dict:
        user = self.user_get(uid)
        user['info']['name'] = name
        user['info']['email'] = email
        user['info']['head'] = head
        self.db.user.update_one({'uid': uid}, {'$set': {'info': user['info']}})
        return user

    def user_update_password(self, uid: int, password: str) -> dict:
        user = self.user_get_with_password(uid)
        user['password'] = password
        self.db.user.update_one({'uid': uid}, {'$set': {'password': user['password']}})
        return user

    def user_get_by_name(self, name: str):
        return self.delete_user_password(self.db_get_one('user', {'name': name}))

    def user_get_by_cid(self, cid: int):
        users = self.db_get('user', {'cid': cid})
        users = map(lambda x: self.delete_user_password(x), users)
        return users

    def user_new(self, name: str, cid: int, password: str, type_: str,
                 head: str = None, email: str = None) -> dict:
        try:
            self.user_get_by_name(name)
            raise XSubmitDB.XSubmitDBExceptions.ObjectExist
        except XSubmitDB.XSubmitDBExceptions.ObjectNotFound:
            pass
        user = {
            'uid': self.get_next_id('user'),
            'type': type_,
            'password': password,
            'cid': cid,
            'created_at': int(time.time()),
            'info': {
                'name': name,
                'head': head,
                'email': email
            }
        }
        user = self.delete_none_node(user)
        self.db.user.insert_one(user)
        user = self.delete_id_node(user)
        return user

    def user_remove(self, uid: int):
        user = self.user_get(uid)
        self.db.user.delete_one({'uid': uid})
        return user

    def post_get(self, pid: int) -> dict:
        return self.db_get_one('post', {'pid': pid})

    def post_get_by_class(self, cid: int) -> dict:
        return self.db_get_one('post', {'cid': cid})

    def post_get_by_uid(self, uid: int) -> list:
        return self.db_get('post', {'uid': uid})

    def post_get_by_reply(self, pid: int) -> list:
        return self.db_get('post', {'reply_to': pid})

    def post_new(self, title: str, uid: int,
                 cid: int = None,  # 如果没有cid可以算作私发，没有cid必须要有targets
                 text: str = None,
                 targets: list = None,  # 当cid有且targets为None，直接找所有同学
                 # 如果想同时发送给老师，需要加上targets的内容。
                 # 即cid表示必须包含一个班同学
                 sub_title: str = None,
                 files: list = None,
                 dead_line: int = None,
                 plugins: list = None,
                 reply_to: int = None):
        if targets is None and cid is None and reply_to is None:
            raise XSubmitDB.XSubmitDBExceptions.ArgsError
        post = {
            'pid': self.get_next_id('post'),
            'cid': cid,
            'text': text,
            'uid': uid,
            'targets': targets,
            'reply_to': reply_to,
            'info': {
                'title': title,
                'sub_title': sub_title,
                'files': files,
                'dead_line': dead_line,
                'plugins': plugins
            }
        }
        post = self.delete_none_node(post)
        self.db.post.insert_one(post)
        post = self.delete_id_node(post)
        return post

    def post_update_info(self, pid: int,
                         title: str = None,
                         text: str = None,
                         sub_title: str = None,
                         files: list = None,
                         dead_line: int = None,
                         plugins: list = None):
        post = self.post_get(pid)
        post['info']['title'] = title
        post['info']['text'] = text
        post['info']['sub_title'] = sub_title
        post['info']['files'] = files
        post['info']['dead_line'] = dead_line
        post['info']['plugins'] = plugins
        self.db.post.update_one({'pid': pid}, {'$set': {'info': post['info']}})
        return post

    def post_remove(self, pid: int):
        post = self.post_get(pid)
        self.db.post.delete_one({'pid': pid})
        return post

    def box_add(self, uid: int, pid: int) -> dict:
        box = {
            'bid': self.get_next_id('box'),
            'uid': uid,
            'pid': pid,
            'state': 'unread'
        }
        self.db.box.insert_one(box)
        return self.delete_id_node(box)

    def box_find(self, bid: int) -> dict:
        return self.db_get_one('box', {'bid': bid})

    def box_find_by_uid(self, uid: int) -> list:
        return self.db_get('box', {'uid': uid})

    def box_remove(self, bid: int) -> dict:
        box = self.box_find(bid)
        self.db.box.delete_one({'bid': bid})
        return box

    def box_remove_by_uid(self, uid: int) -> None:
        self.db.box.delete_many({'uid': uid})

    def box_read_one(self, bid: int):
        self.db.box.update_one({'bid': bid}, {'$set': {'state': 'read'}})

    def box_read_all_by_uid(self, uid):
        self.db.box.update_one({'uid': uid}, {'$set': {'state': 'read'}})


def xsubmit_test_all():
    db = XSubmitDB()
    db.database_clear_all()
    db.database_init()

    # 新建一个学校
    school = db.school_new('哈尔滨工业大学（深圳）')
    print(school)
    # 建立一个班级
    class_ = db.class_new(school['sid'], '计科1班')
    print(class_)
    # 建立一个教师用户
    teacher = db.user_new('7老师', class_['cid'], '', 'teacher')
    print(teacher)
    # 建立一个学生用户
    student = db.user_new('提前退学', class_['cid'], '', 'student')
    print(student)


if __name__ == "__main__":
    xsubmit_test_all()
